home *** CD-ROM | disk | FTP | other *** search
- /* A General Reentrant printf() Function
-
- by Philip J. Erdelsky
- CompuServe 75746,3411
- InterNet 75746.3411@compuserve.com
-
- June 8, 1992
-
- PUBLIC DOMAIN -- NO RESTRICTIONS ON USE
-
- The function printf() and its companions fprintf(), sprintf(), vprintf(), etc.,
- are found in every standard C library and are called by nearly every C
- application. However, without source code they are rather difficult to adapt
- to environments other than those for which they were written, and they are not
- necessarily reentrant.
-
- Our version of these functions, which we call general_printf(), is as portable
- and versatile as we could make it, and it is completely reentrant. You may
- have to serialize access to the output device, but you won't have to serialize
- access to general_printf() itself.
-
- We don't support floating-point editing, but we do support other features of
- the original printf() and also some add-ons. The editing phrase %b edits an
- integer in binary, and the phrase %u edits an unsigned integer in decimal.
- Both permit the standard field parameter and the modifier l if the integer is
- long. An asterisk, if used as a field or precision parameter, takes on the
- value of the corresponding argument, which must be an integer. The editing
- phrase %x uses small letters in the edited result, the editing phrase %X uses
- capital letters.
-
- We tacitly assume that function arguments of type "short" and "char" are
- expanded to type "int", and that the size of an argument of type "long" or
- "char *" is a multiple of the size of an argument of type "int". These
- assumptions are true for all compilers that we are familiar with.
-
- The call on general_printf() is as follows:
-
- n = general_printf(output_function, output_pointer, control_string,
- argument_pointer);
-
- void (*output_function)(void *, int); function to be called to output
- a character of the edited result
-
- void *output_pointer; pointer to be passed to
- (*output_function)()
-
- char *control_string; control string, also called a
- format string
-
- int *argument_pointer; pointer to first argument in
- argument list
-
- int n; number of characters sent to
- output device, or a negative
- error code
-
- The function uses the control string to edit the argument list and sends the
- resulting string of ASCII characters to the output device, one at a time, by
- calling the function (*output_function)() as follows:
-
- e = (*output_function)(output_pointer, c);
-
- void *output_pointer; implementation-defined pointer
-
- int c; character of edited string
-
- int e; nonnegative value for successful operation,
- or negative error code
-
- The pointer may be a file pointer, a drive number, or a pointer to some
- implementation-defined descriptor block. The function general_printf() makes
- no assumptions about it, but merely passes it on. The function need not
- return the character c if the operation is successful, but it must return a
- negative value if there was an error. This value is then returned unchanged
- by general_printf() as its functional value.
-
- The function general_printf() need not be called directly. You may find it
- much more convenient to use it as a basis for your own versions of the standard
- C library functions. Here are examples of possible alternative versions of
- fprintf() and sprintf() under DOS:
-
- #include <stdio.h>
-
- static int output(void *fp, int c)
- {
- return putc(c, (FILE *) fp);
- }
-
- int alternative_fprintf(FILE *fp, char *control, ...)
- {
- return general_printf(output, fp, control, (int *)(&control+1));
- }
-
- static int fill_string(void *p, int c)
- {
- *(*(char **)p)++ = c;
- return 0;
- }
-
- int alternative_sprintf(char *s, char *control, ...)
- {
- int n = general_printf(fill_string, &s, control, (int *)(&control+1));
- *s = 0;
- return n;
- }
-
- The implementation of general_printf() uses "short" arithmetic in a few places
- where "int" arithmetic might have been used. Of course, if the two types are
- the same, there is no harm in doing this; but if "short" variables occupy two
- bytes and "int" variables occupy four bytes, some stack space is saved by using
- "short" variables. However, there is also a much better reason. If
- general_printf() is given corrupt input, it may hang up in one of its internal
- loops. If the loop counter is four bytes long, the system or process may
- effectively freeze because executing even a tight loop over 2 billion times
- will take longer than the user is prepared to wait. If the loop counter is
- only two bytes long, it will finish after at most 32,767 iterations.
-
- -----------------------------------------------------------------------------*/
-
- #define BITS_PER_BYTE 8
-
- struct parameters
- {
- int number_of_output_chars;
- short minimum_field_width;
- char options;
- #define MINUS_SIGN 1
- #define RIGHT_JUSTIFY 2
- #define ZERO_PAD 4
- #define CAPITAL_HEX 8
- short edited_string_length;
- short leading_zeros;
- int (*output_function)(void *, int);
- void *output_pointer;
- };
-
- static void output_and_count(struct parameters *p, int c)
- {
- if (p->number_of_output_chars >= 0)
- {
- int n = (*p->output_function)(p->output_pointer, c);
- if (n>=0) p->number_of_output_chars++;
- else p->number_of_output_chars = n;
- }
- }
-
- static void output_field(struct parameters *p, char *s)
- {
- short justification_length =
- p->minimum_field_width - p->leading_zeros - p->edited_string_length;
- if (p->options & MINUS_SIGN)
- {
- if (p->options & ZERO_PAD)
- output_and_count(p, '-');
- justification_length--;
- }
- if (p->options & RIGHT_JUSTIFY)
- while (--justification_length >= 0)
- output_and_count(p, p->options & ZERO_PAD ? '0' : ' ');
- if (p->options & MINUS_SIGN && !(p->options & ZERO_PAD))
- output_and_count(p, '-');
- while (--p->leading_zeros >= 0)
- output_and_count(p, '0');
- while (--p->edited_string_length >= 0)
- output_and_count(p, *s++);
- while (--justification_length >= 0)
- output_and_count(p, ' ');
- }
-
-
- int general_printf(int (*output_function)(void *, int), void *output_pointer,
- char *control_string, int *argument_pointer)
- {
- struct parameters p;
- char control_char;
- p.number_of_output_chars = 0;
- p.output_function = output_function;
- p.output_pointer = output_pointer;
- control_char = *control_string++;
- while (control_char != '\0')
- {
- if (control_char == '%')
- {
- short precision = -1;
- short long_argument = 0;
- short base = 0;
- control_char = *control_string++;
- p.minimum_field_width = 0;
- p.leading_zeros = 0;
- p.options = RIGHT_JUSTIFY;
- if (control_char == '-')
- {
- p.options = 0;
- control_char = *control_string++;
- }
- if (control_char == '0')
- {
- p.options |= ZERO_PAD;
- control_char = *control_string++;
- }
- if (control_char == '*')
- {
- p.minimum_field_width = *argument_pointer++;
- control_char = *control_string++;
- }
- else
- {
- while ('0' <= control_char && control_char <= '9')
- {
- p.minimum_field_width =
- p.minimum_field_width * 10 + control_char - '0';
- control_char = *control_string++;
- }
- }
- if (control_char == '.')
- {
- control_char = *control_string++;
- if (control_char == '*')
- {
- precision = *argument_pointer++;
- control_char = *control_string++;
- }
- else
- {
- precision = 0;
- while ('0' <= control_char && control_char <= '9')
- {
- precision = precision * 10 + control_char - '0';
- control_char = *control_string++;
- }
- }
- }
- if (control_char == 'l')
- {
- long_argument = 1;
- control_char = *control_string++;
- }
- if (control_char == 'd')
- base = 10;
- else if (control_char == 'x')
- base = 16;
- else if (control_char == 'X')
- {
- base = 16;
- p.options |= CAPITAL_HEX;
- }
- else if (control_char == 'u')
- base = 10;
- else if (control_char == 'o')
- base = 8;
- else if (control_char == 'b')
- base = 2;
- else if (control_char == 'c')
- {
- base = -1;
- p.options &= ~ZERO_PAD;
- }
- else if (control_char == 's')
- {
- base = -2;
- p.options &= ~ZERO_PAD;
- }
- if (base == 0) /* invalid conversion type */
- {
- if (control_char != '\0')
- {
- output_and_count(&p, control_char);
- control_char = *control_string++;
- }
- }
- else
- {
- if (base == -1) /* conversion type c */
- {
- char c = *argument_pointer++;
- p.edited_string_length = 1;
- output_field(&p, &c);
- }
- else if (base == -2) /* conversion type s */
- {
- char *string;
- p.edited_string_length = 0;
- string = * (char **) argument_pointer;
- argument_pointer += sizeof(char *) / sizeof(int);
- while (string[p.edited_string_length] != 0)
- p.edited_string_length++;
- if (precision >= 0 && p.edited_string_length > precision)
- p.edited_string_length = precision;
- output_field(&p, string);
- }
- else /* conversion type d, b, o or x */
- {
- unsigned long x;
- char buffer[BITS_PER_BYTE * sizeof(unsigned long) + 1];
- p.edited_string_length = 0;
- if (long_argument)
- {
- x = * (unsigned long *) argument_pointer;
- argument_pointer += sizeof(unsigned long) / sizeof(int);
- }
- else if (control_char == 'd')
- x = (long) *argument_pointer++;
- else
- x = (unsigned) *argument_pointer++;
- if (control_char == 'd' && (long) x < 0)
- {
- p.options |= MINUS_SIGN;
- x = - (long) x;
- }
- do
- {
- int c;
- c = x % base + '0';
- if (c > '9')
- {
- if (p.options & CAPITAL_HEX)
- c += 'A'-'9'-1;
- else
- c += 'a'-'9'-1;
- }
- buffer[sizeof(buffer) - 1 - p.edited_string_length++] = c;
- }
- while ((x/=base) != 0);
- if (precision >= 0 && precision > p.edited_string_length)
- p.leading_zeros = precision - p.edited_string_length;
- output_field(&p, buffer + sizeof(buffer) - p.edited_string_length);
- }
- control_char = *control_string++;
- }
- }
- else
- {
- output_and_count(&p, control_char);
- control_char = *control_string++;
- }
- }
- return p.number_of_output_chars;
- }
-
-